home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
CD ROM Paradise Collection 4
/
CD ROM Paradise Collection 4 1995 Nov.iso
/
games_d
/
oasys.zip
/
OASYS.DOC
< prev
Wrap
Text File
|
1991-03-03
|
80KB
|
1,950 lines
1: Introduction
OASYS stands for Object-Oriented Adventure System. It is a system designed
for writing text adventure games. The "Object-Oriented" in the title means
that the system is built around the objects that appear in the game. This
means that you can write adventure games faster and more easily than with
older systems.
The system and this manual are designed under the assumption that you know
the basics of how to use a computer but are not a programmer. From time to
time you will come across technical notes in the manual, enclosed in
square brackets [like this]. These contain extra information that you
don't need to know about to use the system, and you can completely ignore
them if you like.
[OASYS contains a general-purpose object-oriented programming language
that could be used to write many different types of programs, however it's
mostly designed for adventure games.]
2: Files
You should have the following files:
OAC The compiler program
OAD The disassembler program
OAI The interpreter program
EXAMPLE.S A simple example adventure game
ESCAPE.S A full-length example adventure game
OASYS.PRN A version of this manual suitable for printing out
OASYS.DOC A version of this manual suitable for reading on the screen.
You may have the following files which are the source code for OASYS
itself. You don't need them to use OASYS but if you are a programmer you
can use them to find out how the system works and to modify it:
INS.H
OAC.C
OAD.C
OAI.C
RWLIB.H
RW.LIB
File names have been given in upper case here for clarity. On most
computers it doesn't matter whether you type file names in upper or lower
case. However if you are using a UNIX system, you must type all file names
in lower case.
3: Usage
How do you create an adventure game with OASYS? There are two stages in
the process:
First, you must write the game in the OASYS game description language.
This can be done with any text editor or word processor. However if you
are using a word processor, you must make sure that it can write files in
plain ASCII format - if in doubt, check the manual for your word
processor.
Suppose you were writing a game called Zork (this is the name of one of
the most famous adventure games ever written). And suppose you are using
WordStar to write the game description. You would type:
WS ZORK.S
to create a file called ZORK.S. (OASYS expects the file name to end with
".S"). This is called the "source file" or "source code" - hence the S for
"source" at the end of the file name.
Second, you must translate the human-readable source code into "object
code" which can be run on the computer. This is done by typing:
OAC ZORK.S
or:
OAC ZORK
(note that it doesn't matter whether or not you type the ".S" at the end
of the source file name). This will cause the OAC program (the "compiler")
to translate the human-readable ZORK.S file into the machine-readable ZORK
file.
Finally, to play the game you type:
OAI ZORK
This will cause the OAI program (the "interpreter") to read the ZORK file
and run the game. To give your adventure game to other people you must
give them the ZORK file and the OAI program. You may choose to give them
the ZORK.S file if you want them to see how your adventure is written. And
you may choose to give them the OAC file as well if you want them to be
able to change your adventure (by changing ZORK.S and typing OAC ZORK).
So to summarize, there are two files and two programs involved:
ZORK.S is the human-readable "source file" that you create with a word
processor.
ZORK is the machine-readable "object file" that can be played.
OAC is the program that translates the source file into the object file.
OAI is the program that runs the object file.
Word Processor -> ZORK.S -> OAC -> ZORK -> OAI -> Player
You can try all this out with the two example adventure games supplied
with OASYS. To translate and run EXAMPLE, type:
OAC EXAMPLE
OAI EXAMPLE
Similarly you can translate and run ESCAPE.
What happens if you accidentally type OAI ZORK.S, in other words giving
OAI the human-readable source file instead of the machine-readable object
file? It won't do any harm because OAI knows what object files are
supposed to look like and will reject anything else with an error message.
What about the "disassembler" program mentioned above? You don't actually
need it at all. It was written to help debug the other two programs, and
included in the package in case anyone was interested in it. What it does
is to let you examine the object file. You use it by typing:
OAD ZORK
It will generate a listing of the internal structure of the object file.
Note that this listing is not in any way a substitute for the original
source file. Also note that the listing is very long! If you want to try
out the program, try it out on EXAMPLE first. If you decide to try it on
ESCAPE, you can press CTRL-C to stop it.
Of course, you will have to translate EXAMPLE.S and ESCAPE.S into EXAMPLE
and ESCAPE with the OAC program before you can try out OAD.
[Actually you can give the source file any extension you like. However if
it's anything other than ".S" you must supply the extension when running
OAC.]
4: Portability
Having read the rest of the manual you will know how to write an adventure
game with OASYS, and having read the previous section you will know how to
translate and run it. Now what happens when you want to port the game?
"Port" means "make work on a different type of computer". Suppose you've
written a game on a PC-compatible machine and you want it to run on a UNIX
system, you must do the following:
First, get a version of OASYS for the UNIX system. You cannot port OASYS
itself unless you are a programmer, so if you can't get a version for UNIX
you'll have to forget about it for the moment.
Now, copy the source file to the UNIX system. Don't try to copy the object
file! The format it's stored in varies for different types of computers,
so the UNIX version of OAI won't understand an object file created by the
PC version of OAC. However it will *think* it can understand it and will
try to run it, probably crashing the computer.
Now, run the OAC program on the UNIX system to create a version of the
object file that'll work on UNIX. Then run it with OAI.
Note that only one file - your source file - ever got copied from the PC
to the UNIX computer.
[Actually it *may* be possible to copy the object file directly. It will
only work if source and destination machines have the same byte ordering
and the same int size.]
[If you haven't got a version of OASYS for a target system, you're welcome
to try porting it yourself - it shouldn't be too hard provided you have a
C++ compiler for the target system.]
5: Language
This section of the manual contains information on how to write your
source code. It is suggested that you print out EXAMPLE.S and ESCAPE.S and
refer to them as you read this section of the manual, since a few examples
are usually better than any amount of explanation. Also you may need to
scan through the manual several times before you understand all of it.
5.1: Case
All examples have been given in upper case for clarity. However it
doesn't matter whether you type your code in upper or lower case.
(Unlike filenames, this is true even on UNIX systems). I personally
prefer lower case and EXAMPLE.S and ESCAPE.S are all in lower case.
5.2: Spaces
OASYS regards any amount of "whitespace" characters (space, tab or new
line) as being the same as a single space. This means that whenever you
see two words or symbols separated by a space in the example code, you
could put in any number of spaces or even put them on separate lines.
Conversely, if you see two things on separate lines, you could put them
on the same line separated by a space. For the sake of clarity it is
the convention to put no more than one definition, statement etc. on a
line but you need not stick to this.
5.3: Identifiers
The names of all kinds of things (e.g. object classes, methods etc.)
must be one word "identifiers". An identifier is a sequence of letters
and digits but it must begin with a letter. So
ROOM
SATURN5
HYDROGENBOMB
are valid identifiers, but
9TO5
is not (OASYS will look at the 9 at the front and think it's a number,
then get confused when it sees the TO).
So you can't use more than one word for an identifier - but the
underscore character "_" is actually counted by OASYS as a letter! So
the above could be written as
ROOM
SATURN_5
HYDROGEN_BOMB
_9_TO_5
Note that the last is now legal! And the use of "_" makes the names
more readable. However remember that "_" is NOT the same as the hyphen
"-".
5.4: Comments
Comments may be inserted anywhere at all in your source code. These are
"marginal notes" to explain how something works, for the benefit of
other people who want to read your code or for your own benefit if you
come back in six months time. There are two ways to define comments:
Anything from the symbol // to the end of the line is a comment e.g.
... // This is a comment
Anything between the symbols /* and */ is a comment e.g.
/* This is
another comment */
The // comments are better for one-line notes whereas the /*...*/
comments are better for multi-line explanations.
Another use for comments is "commenting out" code. Suppose you have a
chunk of code you want to remove but you might want to put it back in
again later. You don't want to actually delete it because then if you
do want to put it back in you'll have a lot of unncessary typing to do.
So you enclose it it comment symbols - then OASYS will ignore it but
you can put it back in anytime just by deleting the comment symbols.
Either put // at the beginning of each line or put /* at the beginning
of the code and */ at the end.
When commenting out code, remember that you can't nest /*...*/ comments
e.g. suppose you have commented out a method definition as follows:
/*
METHOD SOMETHING
{
...
/* This is a comment
inside the method definition */
...
}
*/
OASYS will think the first */ inside the method definition corresponds
to the /* before the method definition. So it will only see the second
half of the method definition, and an apparently unmatched */ symbol!
Where you have /*...*/ comments inside something you want to comment
out, you should do the commenting out with // symbols.
5.5: Order of definitions
When writing the various sections of your source code, remember that
OAC only scans through the source file *once* for the sake of speed.
This means that everything defined in one part of the source code and
referred to in another part must be defined *before* it is referred to.
5.6: Objects
OASYS is based on objects. Unlike other systems, even locations and the
player are objects. When a game is starting, no objects exist. You must
arrange for some objects (such as the player and locations!) to be
created at the start. Later more objects may be created and old ones
destroyed. Every object belongs to a class, so the first thing to do is
usually to define what classes you are going to have. (Remember
everything must be defined before it's referred to and you're going to
refer to your classes a lot!) For example:
CLASS PLAYER {{ME} {SELF} {MYSELF}}
CLASS ROOM {}
CLASS MACHINE_GUN {{GUN} {MACHINE GUN}}
The first of these is the class to which the player object is going to
belong. The things after the word PLAYER mean that the (human) player
may refer to himself with the words ME, SELF or MYSELF e.g. EXAMINE
MYSELF or KILL SELF. The empty pair of curly brackets after the word
ROOM means that the player cannot refer to objects of class ROOM. The
definition of CLASS MACHINE_GUN says that the player can refer to
objects of this class with the word GUN or the phrase MACHINE GUN, so
GET GUN or GET MACHINE GUN will mean the same thing.
In general, a class definition consists of the word CLASS followed by
the class name, an open curly bracket "{", a list of phrases which can
be used to refer to objects of that class, and a close curly bracket
"}". Each phrase is an open curly bracket, a list of words and a close
curly bracket. The reason you need the curly brackets around the list
of words is to tell OASYS when the list of words is finished, and the
reason you need the curly brackets around the list of phrases is to
tell OASYS when the list of phrases is finished.
Note that unlike many systems, OASYS lets you have many objects
belonging to the same class, so you can have say several identical
MACHINE_GUNs. Of course all your locations will be objects of class
ROOM. (Unless you decide for some reason to have some locations of say
class TUNNEL or TREETOP or whatever).
5.7: Properties
As well as belonging to classes objects have properties, such as
description and weight. Unlike other systems OASYS starts with no
preconceived idea of what properties objects are going to have. You
must define the list of properties. When you have done this each object
in your game will possess different values for these properties, for
example:
PROPERTY INT WEIGHT
PROPERTY STRING DESCRIPTION
PROPERTY OBJECT IN
As you can see, each property in the list starts with the word
PROPERTY. Then is the property type, which can be INT, STRING or
OBJECT. Last comes the name of the property.
INT properties are a number, in this example WEIGHT. These properties
are called INT because the number must be an integer i.e. a whole
number. (Numbers with decimals may be allowed in a future version of
OASYS).
STRING properties are a piece of text. The name STRING comes from the
fact that each is a "string" of letters, digits, punctuation marks or
whatever.
OBJECT properties are a reference to another object.
So how would the above be used? All objects of class MACHINE_GUN might
be assigned a WEIGHT of 15 when created; objects of other classes might
be assigned different WEIGHTs. (Of course you could also assign
different WEIGHTs to different MACHINE_GUNs).
WEIGHT is not useful for objects of class ROOM. All objects will have a
WEIGHT of 0 when created, so you would not bother to change this for
ROOMs.
DESCRIPTION would be used for almost all types of objects. (An
exception might be the player). For example, one ROOM might have a
DESCRIPTION of "Long dark tunnel" whereas another ROOM might have a
DESCRIPTION of "Large hall". All MACHINE_GUNs might have a DESCRIPTION
of "A machine gun".
All STRING properties have an initial value of "*NULL STRING*" so that
if you forget to assign the proper value to one you will notice quickly
when testing the game.
IN in this example is used to indicate what other object this object is
contained in. For example, the player's IN value might indicate which
ROOM he is in. An object's IN value might refer to a ROOM object. Or it
might refer to the player object to mean that the object was being
carried by the player.
All OBJECT properties start off with a value of OBJECT 0 which means no
object at all. (The exact meaning of the expression OBJECT 0 is
explained in the "Expressions" section of the manual). You would leave
a ROOM's IN property as OBJECT 0 because ROOMs are not IN anything.
5.8: Methods
Methods are how everything gets done in OASYS. For example:
METHOD INT SQUARE INT X
{
RETURN X * X
}
is a method which will calculate the square of a number. The definition
consists of:
the word METHOD
the name of the method, SQUARE
the word INT to indicate that it returns an INT value. (Types that
methods can return are INT, STRING and OBJECT, the same as for
properties).
one "argument" passed to the method. This method is called X and is of
type INT. (Types for method arguments are INT, STRING and OBJECT).
an open curly bracket to indicate the start of the actual method code.
the method code, in this case just one line to cause the square of the
argument X to be returned.
a close curly bracket to indicate the end of the method definition.
Now here is another method:
METHOD LOOK VERBS {{LOOK} {DESCRIBE LOCATION}}
{
PRINT PLAYER IN DESCRIPTION
}
This consists of:
the word METHOD
*no* type name for the method - this is because this method does not
return anything at all.
the name of the method, LOOK
the word VERBS
a list of phrases which the player can type to call this method - this
is the same as the list of nouns for a CLASS.
the method code as above - this time the method code prints the
description of the ROOM the player is IN, but does not RETURN anything.
So some methods are actually verbs. Whenever the (human) player types a
command, OASYS searches for a method with a corresponding verb. If
found, the method is called. The method SQUARE had no VERBS and so was
purely for internal use.
A method with VERBS can't have any STRING arguments. (This limitation
may be removed in future versions). It can have INT arguments, but the
player is not allowed to type negative numbers. (This limitation may
also be removed in future versions). However if the method is also
called by other methods, the other methods can supply negative numbers
for the INT arguments even though the player can't.
Suppose you want a method with both verbs and arguments? You can
specify the position of the arguments in the verbs e.g.
METHOD GIVE_TO OBJECT X IS_CARRIED OBJECT Y IS_VISIBLE
VERBS {{GIVE X TO Y} {OFFER X TO Y} {GIVE Y X} {OFFER Y X}}
{
...
}
What this means is that if the player wants to give something to
something else he must type the "give" command with the names of the
two objects. The first object, which is being given, must be carried by
the player. The second object, which is being given the first object,
must be visible. (For further explanation of IS_CARRIED and IS_VISIBLE,
see below). The alternative ways the player can phrase the command are
given in the VERBS definition. There is an example in ESCAPE.S where
the player must give a fish to a tiger, and the alternative ways this
could be phrased are:
GIVE FISH TO TIGER
OFFER FISH TO TIGER
GIVE THE TIGER THE FISH
OFFER THE TIGER THE FISH
The word THE, inserted above for clarity, is always ignored when the
player types in a command so that is why it is not present in the VERBS
definition. You can use more informative names than X and Y for the
method arguments but whatever names you use must be correctly placed in
the VERBS definition e.g.
METHOD GIVE_TO OBJECT GIFT IS_CARRIED OBJECT RECIPIENT IS_VISIBLE
VERBS {{GIVE GIFT TO RECIPIENT} {OFFER GIFT TO RECIPIENT}
{GIVE RECIPIENT GIFT} {OFFER RECIPIENT GIFT}}
{
...
}
would also work.
5.9: Variables
As well as properties you can have "global variables" which are like
properties but there's only one of them. For example,
INT SCORE
could record the current score in the game. You only want this recorded
once not for every object, so you make it a global variable rather than
a property. This is done by only having the type (INT, STRING or
OBJECT) and the name, without the word PROPERTY.
Also you can have what are called "local variables" within methods. You
define them exactly like global variables, but put the definition
*inside* the method definition, just before the method code. e.g.
METHOD SQUARE could be rewritten:
METHOD INT SQUARE INT X
{
INT RESULT
RESULT = X * X
RETURN RESULT
}
The variable RESULT only exists within METHOD SQUARE. You could define
STRING RESULT in another method elsewhere in the source code and there
would be no conflict.
Note that it is the convention to separate the variable definition(s)
from the method code with one blank line (but this is not required).
Why use local variables at all when global variables will do the same
job? Well, if you only want to use a variable within one method, making
it a local variable makes it a lot easier to keep track of what you're
doing because if you forget where you used it you have a lot less code
to search through. Also they're essential for "recursion" (q.v.).
You can't have two global variables with the same name, or two local
variables with the same name within the same method. What if you have a
global variable, and within a method a local variable with the same
name? e.g. suppose in the above example there was a global variable
OBJECT RESULT already defined? No problem. Within METHOD SQUARE, RESULT
would be taken to mean the local INT RESULT. Within other methods,
RESULT would be taken to mean the global OBJECT RESULT.
Arguments to methods have the same status as local variables. For
example, in METHOD SQUARE it would not have been legal to have a local
variable STRING X because it would conflict with the argument INT X.
5.10: THIS
All methods are considered to be "applied to" an object, for example
methods that correspond to typed commands are considered to be applied
to the PLAYER object. The way this works is that all methods have an
implicit argument OBJECT THIS which is whatever the method is applied
to.
So suppose you have a METHOD GET which is called when the player wants
to GET an object. THIS will then be the player object. On the other
hand if the player tells something else to GET something (see below for
how to do this), THIS will be that something else. So you can write the
method to distinguish between when THIS is the player (in which case
the command will be carried out) and when THIS is not the player (in
which case there may be a refusal to carry out the command!). For
another example of the use of THIS, see "selector methods".
On the other hand for methods like SQUARE above which give the same
result no matter what object they're applied to, THIS won't be used at
all, although it must still be supplied.
Whenever a method is called applied to OBJECT 0, it will return
immediately without executing any of the method code. The return value
if any will be set to 0 for an INT method, "*NULL STRING*" for a STRING
method or OBJECT 0 for an OBJECT method.
5.11: METHOD INIT
Methods with verbs are called when the player types commands, and this
is how things get done. However when the game starts some objects need
to be created initially and other setting up may also need to be done.
The way this is done is, you must have a method defined as follows:
METHOD INIT
{
...
}
i.e. it must not return a value or have arguments or verbs (though it
may have local variables). This method will get called at the start of
every game.
5.12: Selector methods
Consider this definition (used in the section on "Methods"):
METHOD GIVE_TO OBJECT X IS_CARRIED OBJECT Y IS_VISIBLE
VERBS {{GIVE X TO Y} {OFFER X TO Y} {GIVE Y X} {OFFER Y X}}
{
...
}
The IS_CARRIED and IS_VISIBLE are called "selector methods". The point
is, suppose you have many MACHINE_GUNs in the game and the player types
GIVE MACHINE GUN TO GUARD, and the player is only carrying one machine
gun, obviously you want your GIVE_TO method to be called with the
correct machine gun, otherwise the GIVE_TO method will only see that
the player does not have X, it will not see that there is another
machine gun that he does have, so it will idiotically respond that the
player does not have the machine gun.
Also in traditional adventure writing systems, the writer has to waste
a great deal of time checking, for each and every possible command,
that the player has or can see the objects referred to. So you want the
checking to be done *before* your GIVE_TO or whatever method is called.
Selector methods are the answer to this. The above tells OASYS, "for
argument X, call method IS_CARRIED to check whether it's valid; for
argument Y, call method IS_VISIBLE to check whether it's valid". This
will produce the desired result if you have previously defined
something like:
METHOD INT IS_CARRIED
"You haven't got that.\n"
{
RETURN THIS IN == PLAYER
}
METHOD INT IS_VISIBLE
"That isn't here.\n"
{
RETURN THIS IS_CARRIED OR THIS IN == PLAYER IN
}
Of course you can have as many selector methods as you like and call
them whatever you like but the above two are widely useful.
More complicated versions of IS_CARRIED and IS_VISIBLE are defined in
ESCAPE.S to cope with the possibility that for example, something might
still be visible if it was in an object that itself was in the player's
location.
Selector methods take no arguments. THIS represents the object which is
being tested. The selector method must return an INT value, which is
zero if THIS is not valid and non-zero if THIS is valid.
The two messages just before the open curly brackets for the two
methods are the messages that will be displayed if the object is found
not to be carried or visible respectively. (Such messages should only
be defined for selector methods, not for other methods).
Why can the message not simply be displayed by the selector method
itself if THIS is found to be invalid, instead of having to be
specially defined for OASYS to display? Because suppose the player
types GIVE MACHINE GUN TO GUARD and you have many machine guns in the
game, none of which is in the player's possession. Each and every one
of them will be checked for IS_CARRIED but you only want one "You
haven't got that" message to be displayed.
Of course if you know that you only have one object of each class in
your game, you can have the message printed by the selector method
instead of defining it before the open curly bracket (say if you want
to give a more specific message like "You haven't got the machine
gun.").
5.13: METHOD SELECT_ADDRESSEE
The player can talk to other objects in the game! The command for this
is for example "GUARD, DROP MACHINE GUN" will give the command to drop
the machine gun to the guard as typing "DROP MACHINE GUN" would give
the command to drop the machine gun to the player object. Of course you
would probably have programmed your METHOD DROP to check if the DROP
command was being given to something other than the player, and if so
not necessarily carry out the order!
In general, to give a command to another object, the player must type
the name of the object to be addressed, followed by a comma and then
the rest of the command.
The problem arises again here of how to select which objects the player
can talk to, just as it did earlier of how to select which objects a
command can apply to. For example, if you have many guards in the game
you want to make sure the one the player ends up talking to is the one
in the same location as the player.
There is a special selector method for things the player can talk to.
It's called METHOD SELECT_ADDRESSEE. If you have this defined it will
be used, if you haven't got it defined the player will just get the
message "You can't talk to that" whenever he tries to talk to anything.
An example is:
METHOD SELECT_ADDRESSEE
"It doesn't understand you!\n"
{
RETURN (THIS IS GUARD OR THIS IS ROBOT) AND THIS IS_VISIBLE
}
which will only let you talk to GUARDs or ROBOTs which are visible. (Of
course, you must have previously defined the selector method
IS_VISIBLE).
If you don't have the message defined for METHOD SELECT_ADDRESSEE it
will default to "You can't talk to that".
5.14: Player
The player is represented by an object. There is a predefined global
variable OBJECT PLAYER which tells OASYS which is the player object.
Suppose you have defined
CLASS PLAYER {{ME} {SELF} {MYSELF}}
(which will let the player type commands like EXAMINE SELF and KILL
MYSELF), you must put into METHOD INIT something along the lines of:
PLAYER = CREATE PLAYER
Of course, you don't have to have a CLASS PLAYER defined; if you don't
want the player to be able to type commands that refer to himself and
you have already defined
CLASS ROOM {}
for locations, you could have
PLAYER = CREATE ROOM
in METHOD INIT, and it would work perfectly well.
This means that you can have a facility in your game whereby the player
can take on the identity of many different characters at different
times. For example in the game "Lord of the Rings" on the Commodore 64,
the four hobbits Frodo, Sam, Merry and Pippin appeared as characters
and the player could play any of them with the BECOME command.
Supposing the player started off playing FRODO, he could type BECOME
SAM. This could be implemented in OASYS like this:
CLASS FRODO {{FRODO}}
CLASS SAM {{SAM}}
CLASS MERRY {{MERRY}}
CLASS PIPPIN {{PIPPIN}}
...
OBJECT FRODO
OBJECT SAM
OBJECT MERRY
OBJECT PIPPIN
...
METHOD INT IS_BECOMABLE
{
RETURN THIS SELECT_ADDRESSEE AND (THIS IS FRODO OR
THIS IS SAM OR
THIS IS MERRY OR
THIS IS PIPPIN)
}
METHOD BECOME OBJECT X IS_BECOMABLE VERBS {{BECOME X}}
{
PLAYER = X
}
METHOD INIT
{
...
FRODO = CREATE FRODO
SAM = CREATE SAM
MERRY = CREATE MERRY
PIPPIN = CREATE PIPPIN
PLAYER = FRODO // Player starts off playing Frodo
...
}
Note that if you have already defined METHOD SELECT_ADDRESSEE you can
use it to help select which objects are becomable. The above example
assumes that there are some characters in the game which the players
can talk to but not become.
Also you must have different CLASSes for each of the four hobbits
rather than for example just a CLASS HOBBIT because the player has to
be able to distinguish between the four when typing commands.
5.15: Parser
The "parser" is that part of the OAI program which accepts typed input
from the player and calls methods accordingly.
5.15.1: Features
The player may talk to other objects by prefacing the command with
the name of the other object and a comma.
The word THE is ignored, so TAKE MACHINE GUN is the same as TAKE THE
MACHINE GUN (and for that matter the same as THE THE TAKE THE
MACHINE THE GUN).
Verbs and nouns may have multiple words e.g. PICK UP THE MACHINE GUN
is as easy to implement as TAKE THE AXE. However each alternative
form of the noun must be defined in the CLASS definition and each
alternative form of the verb must be defined in the METHOD
definition.
Words in the verb may be interspersed with nouns e.g. PICK THE
MACHINE GUN UP as well as PICK UP THE MACHINE GUN can be
implemented. Again this must be specified in the METHOD definition
with something like VERBS {{GET X} {TAKE X} {PICK UP X} {PICK X
UP}}.
The player may type commands in upper case, lower case or a mixture
of both, it doesn't matter which.
5.15.2: Process
The process of interpreting a command works as follows:
Each word is checked to make sure it appears somewhere in a CLASS or
METHOD definition. If a completely unrecognized word is found, the
player will receive the message "I don't understand the word 'xxx'."
where xxx is the offending word. Due to a quirk of the OAC program
design, the names of method arguments are regarded as valid words
even if they are not included in any verb or noun definitions. (This
may not be true in later versions of OASYS). So incorrect use of
such names will give the player the message "I don't understand
you." rather than the more specific message above.
If there is a comma, the noun before the comma is compared with the
nouns given in CLASS definitions to decide what CLASS the addressee
belongs to. If no such class can be found, the player receives the
message "I don't know who you're trying to talk to." If METHOD
SELECT_ADDRESSEE is not defined, the player receives the message
"You can't talk to that.". Otherwise METHOD SELECT_ADDRESSEE is used
to find which actual object of that class is being addressed. If no
such object can be found, the player receives the message defined in
METHOD SELECT_ADDRESSEE, or "You can't talk to that." if there is no
such message defined.
The command as a whole is compared with the verbs for the various
methods to decide which method is involved. If no method can be
found with a corresponding verb, the player will receive the message
"I don't understand you.".
Each noun is compared with the nouns given in CLASS definitions to
decide which CLASS the noun refers to. Then the selector methods are
used to decide which actual object is being referred to for each
noun. If no object can be found, the player receives the message
defined in the selector method, if any. In either case the player
must type another command.
The method with the corresponding verb is called with the
appropriate arguments. THIS is set to PLAYER if the command was
typed by itself, or the appropriate object if the command had an
addressee.
5.15.3: Omissions
The following features which are found in the parsers in some other
systems are not included in the OASYS parser, although they may be
included in future versions:
The player must type only one command at a time, he cannot type
several commands separated by the word THEN.
The word IT cannot be used to refer to a noun in the previous
command.
The player cannot refer to many objects at once with the words AND
or ALL.
Adjectives as distinct from nouns cannot be used e.g. if you have a
PLASTIC KEY, a METAL KEY and a CERAMIC key you must define the nouns
as PLASTIC KEY, METAL KEY and CERAMIC KEY and the player must type
the noun in full. He cannot just type KEY in the hope that it will
be obvious which key is being referred to.
5.16: Code for methods
5.16.1: Statements
The code for a method consists of a sequence of statements. A
statement may be one of the following:
5.16.1.1: { statements }
A sequence of zero or more statements surrounded by curly
brackets is equivalent to a single statement. This is used for
IF, WHILE and DO...WHILE constructs - if you want more than a
single statement in one of these you must surround them with
curly brackets. The conventional layout for writing statements
inside curly brackets is:
{
statement 1
statement 2
...
}
i.e. put the "{" and "}" on separate lines and indent the
statements inside by one tab space.
5.16.1.2: PRINT expression
The word PRINT followed by an expression will display the
expression on the screen. The expression may be an integer or
string value. The print routine automatically arranges text for
output so that words are not broken up at the ends of lines. It
doesn't output anything until it has collected a whole word e.g.
PRINT "F"
PRINT "R"
PRINT "E"
PRINT "D "
will cause the word FRED to appear on the screen, but nothing
will actually appear until the last PRINT statement with the
space at the end is reached. A space or a new line character "\n"
will cause the collected output to be displayed.
5.16.1.3: RETURN
In methods which don't return a value, the word RETURN by itself
will cause execution of the method to finish. It's normally used
only with an IF statement because a return will happen
automatically if execution reaches the end of the method so you
don't need to use the word RETURN.
5.16.1.4: RETURN expression
In methods which do return a value, the word RETURN must be
followed by an expression of the appropriate type. The value of
the expression will be returned. If execution reaches the end of
the method without encountering a RETURN statement, a default
value will be returned following the same rules as for initial
values of variables i.e. INT methods will return 0, STRING
methods will return "*NULL STRING*" and OBJECT methods will
return OBJECT 0.
5.16.1.5: DESTROY expression
This will destroy the object given by the expression. If the
value of the expression is OBJECT 0 then OAI will stop with an
error message.
When an object is destroyed, OASYS will look at all global
variables and object properties. Any which have OBJECT type and
refer to the destroyed object will be set to object 0. This does
*not* apply to local variables.
So suppose you have a global variable OBJECT DYNAMITE which
refers to some dynamite. When the dynamite gets used up, you will
want to destroy the dynamite object to save memory, and also to
set the global variable DYNAMITE to OBJECT 0 to record the fact
that the dynamite isn't there anymore. You would expect to have
to do:
DESTROY DYNAMITE
DYNAMITE = OBJECT 0
but the second part is done automatically by OASYS so you just
need:
DESTROY DYNAMITE
[There is an exact correspondence between OASYS and C as follows:
OBJECT Pointer
CREATE (q.v.) malloc()
DESTROY free()
OBJECT 0 Null pointer
*except* for the additional behaviour of DESTROY mentioned
above.]
5.16.1.6: EXIT
The EXIT statement will cause the game to end immediately. The
player will be asked if he wants to play again and if he does, a
new game will start.
5.16.1.7: QUIT
The QUIT statement will cause an "Are you sure? (Y/N)" message to
be displayed on the screen and the player will have to type Y, in
which case the game will stop as for the EXIT statement, or N, in
which case the game will continue as if nothing had happened.
QUIT should be used when the player has typed a QUIT command,
whereas EXIT should be used when the player has been killed or
for some other reason the player has no choice about the game
being over.
5.16.1.8: SAVE
The SAVE statement will cause the current state of the game -
global variables and the list of objects - to be saved to disk.
(The player will be prompted for a file name). After the save has
been done the game will continue as normal.
5.16.1.9: expression = expression
The expression on the left hand side of the = sign must be the
name of a global variable, local variable, method argument or a
property of some object. The expression on the right hand side
may be any expression of the same type as the one on the left
hand side. The variable or whatever on the left hand side will be
assigned the value on the right hand side as its new value, e.g.
INT FRED
FRED = 5
5.16.1.10: IF expression statement [ELSE statement]
The expression must be of type INT. The statement will be
executed if and only if the expression has a nonzero value. The
ELSE part, if present, will be executed if and only if the
expression has a zero value e.g.
INT FLAG
...
FLAG = 0
...
IF FLAG PRINT "NOT ZERO\n" ELSE PRINT "ZERO\n"
will cause ZERO to be printed.
5.16.1.11: WHILE expression statement
The expression must be of type INT. The statement will be
executed repeatedly as long as the expression has a nonzero value
e.g.
INT X
X = 5
WHILE X
{
PRINT "X = "
PRINT X
PRINT "\n"
X = X - 1
}
will give the output:
X = 5
X = 4
X = 3
X = 2
X = 1
Care must be taken to ensure that the loop will eventually
terminate. For example, if the statement X = X - 1 had been
omitted in the above example, the code would have kept printing X
= 5 forever. OAI has no way to detect such a condition so the
player would have to stop the program by pressing CTRL-C.
5.16.1.12: DO statement WHILE expression
The DO...WHILE loop is like a WHILE loop except that the
statement is executed before the expression is tested, so it is
always executed once regardless of the value of the expression.
5.16.1.13: BREAK
A BREAK statement will jump out of the enclosing WHILE or
DO...WHILE loop. If the BREAK statement is not in a loop, an
error message will be given.
5.16.1.14: CONTINUE
A CONTINUE statement will jump to just before the end of the
enclosing WHILE or DO...WHILE loop. If the CONTINUE statement is
not in a loop, an error message will be given.
5.16.1.15: expression method_name arguments
A call to a method which does not return a value is a statement.
The expression must be of type OBJECT, and gives the object to
which the method will be applied. This is followed by the name of
the method and then by a list of expressions, one for each of the
method's arguments if any, e.g.
PLAYER IN DESCRIBE "You are in "
assuming a previous definition of:
METHOD DESCRIBE STRING MSG
{
PRINT MSG
PRINT THIS DESCRIPTION
PRINT ".\n"
}
Methods which return a value cannot be called in this way - their
return value must be used. If you don't want to do anything with
the return value, the best thing to do is to assign it to some
variable.
5.16.2: Expressions
Expressions are used in statements. (An attempt to use an expression
on its own in place of a statement will usually result in the error
message "Statement expected"). An expression may be one of the
following:
5.16.2.1: integer
An ordinary integer number is an expression of type INT, e.g. 42,
5, 367. Commas and decimals points are not allowed i.e. 1000 must
be written as 1000 not 1,000 or 1000.0. Negative numbers are
allowed e.g. -65, but these are treated as expressions consisting
of a minus sign followed by a positive number (see below).
There is a limit to how high or low a value numbers in your
adventure can have. It depends on the type of computer system you
have, and also possibly on the version of OASYS you have. Some
types of computer can handle "32-bit" numbers which can range
from -2147483648 to 2147483647 i.e. about minus two billion to
plus two billion, which should be plenty for most purposes! Other
types of computer can handle only "16-bit" numbers which can
range from -32768 to 32767 i.e. minus thirty-two thousand to plus
thirty-two thousand, which should still be enough in most cases.
If it matters then you can find out whether or not your computer
can handle 32-bit numbers:
A guideline is that versions of OASYS on IBM PC-compatible
computers can probably only handle 16-bit numbers whereas those
on other types of system can probably handle 32-bit numbers.
You can find out for sure by copying EXAMPLE.S and putting
something like "PRINT 1000000" into the INIT method. If you get
the right answer i.e. 1000000 on the screen, you can use 32-bit
numbers, if you get the wrong answer then you can only use 16-bit
numbers.
While it is possible for different versions of OASYS to differ in
their ability to handle 32-bit numbers, if your copy of OASYS can
handle 32-bit numbers, it will always be able to do so on any
computer on which it will work at all. This is another reason to
not necessarily throw away your old version of OASYS if you get a
new version (see the section on "Versions").
5.16.2.2: string
A string is anything between pairs of double quote marks. The
quote marks are not counted as part of the string, e.g. if you
have PRINT "HELLO WORLD!" in a method, the following will come up
on the screen when the game is run:
HELLO WORLD!
What if you have a string longer than will fit on one line of
your source code e.g. a location description that extends over
several lines? There are two ways to do this.
First you can just continue the string on the next line e.g.
PRINT "You are in a
large cavern."
The line break between "a" and "large" will be regarded as a
space so the display of the string on the player's screen won't
be messed up no matter how messy it looks in your source code.
Unfortunately even if you have the first line indented as the
PRINT statement above is, you must start subsequent lines at the
beginning of the line, otherwise the indentation will be regarded
as part of the string e.g.
PRINT "You are in a
large cavern."
looks nicer in the source code, but will produce
You are in a large cavern.
on the player's screen.
You must also be careful not to have any spaces after the "a" on
the PRINT statement line, because these are not visible in your
source code but will also appear on the player's screen.
Second, you can stop the string and start it again. As long as
you have nothing else other than spaces, tabs and line breaks
between the close quotes and the next open quotes the whole thing
is regarded as one string e.g.
PRINT "You are in a "
"large "
"cavern."
Here only the stuff inside the quotes is regarded as part of the
string so you can indent the subsequent parts, so your source
code looks nicer, so this is the method used in ESCAPE.S.
However this time you do have to include the spaces between "a"
and "large" and between "large" and "cavern" in the quotes.
The PRINT statement will cause output to go onto the next line
automatically when it reaches the end of the current line so you
don't have to worry much about going onto new lines. However you
*must* force the output to go onto a new line before the player
is asked for the next command otherwise the player will get
output like this:
You are in a large cavern.>*
where the player must type in his next command at the * in the
middle of the line. You can avoid this and force the output to go
onto a new line like this:
PRINT "You are in a large cavern."
PRINT "\n"
or like this:
PRINT "You are in a large cavern.\n"
i.e. a "\" (backslash) character followed by the letter N (upper
or lower case) will force the output to go onto a new line.
Also a "\" character followed by double quotes will cause the
double quotes to be put into the string rather than interpreted
as the end of the string e.g.
PRINT "The giant says \"Fee fie fo fum!\"\n"
will produce:
The giant says "Fee fie fo fum"!
Of course you can avoid the whole problem by just using single
quote marks inside strings e.g.
PRINT "The giant says 'Fee fie fo fum!'\n"
will produce
The giant says 'Fee fie fo fum!'
which works just as well.
[You can put any chacter whatsoever into a string by placing a
"\" followed by the ASCII code for the character as a two-digit
hexadecimal number e.g. \07 will put a beep into the string. This
method must be used to get an actual "\".]
5.16.2.3: expression operator expression
The operator may be either an arithmetic, comparison or logical
operator.
5.16.2.3.1: Arithmetic operators
The arithmetic operators are:
+ addition
- subtraction
* multiplication
/ division
% remainder after division
All take two INT expressions and return an INT expression e.g.
4 + 5
gives 9,
29 / 10
gives 2 (in OASYS, division always rounds down) and
29 % 10
gives 9 (the remainder when 29 is divided by 10).
5.16.2.3.2: Comparison operators
The comparison operators compare two things and return a true
or false result accordingly. For example
A > B
returns true if A is greater than B and false otherwise. There
is no actual type for true or false in OASYS so integers are
used instead. A zero integer means false, a nonzero integer
means true. So
PRINT A > B
will display 1 if A is greater than B and 0 otherwise, and
DO
{
...
}
WHILE 1
is a standard way to keep repeating something forever (or
until a BREAK statement or something similar is executed
within the loop).
== Equals
!= Not equals
> Greater than
< Less than
>= Greater than or equal to
<= Less than or equal to
The first two can be used to compare objects and strings as
well as integers i.e. you can test whether two strings or two
objects are the same or not. The last four can only be used on
integers i.e. it doesn't make sense to try to check whether
one object or string is greater than or less than another.
Why the double equals sign "==" for the operator to test for
equality? To distinguish it from the assignment "=" sign. It
wasn't practical to have them both the same symbol so
something else had to be used for one or other of them. It was
decided to reserve the one-character symbol "=" for assignment
to save typing on the grounds that assignment is done more
often than testing for equality.
Two expressions of type OBJECT are regarded as equal only if
they actually refer to the *same* object, not just if they
refer to objects of the same class or with the same values for
all properties.
Similarly, two strings are regarded as equal only if they are
actually the *same* string, not just if they look identical!
e.g.
"FRED" == "FRED"
returns 0! Whereas given
STRING S1
STRING S2
S1 = "FRED"
S2 = S1
then
S1 == S2
returns 1. (In future versions of OASYS, the result may be 1
in both cases). For further explanation, see the section on
"Efficiency".
"A != B" is of course the same as "NOT (A == B)", similarly
">" is the negation of "<=" and "<" is the negation of ">=".
5.16.2.3.3: Logical operators
The logical operators in OASYS are OR and AND (also see NOT
below). They take true or false values (such as those returned
by the comparison operators) and give true or false values
accordingly e.g.
1 == 2 OR 1 == 1
returns 1 whereas
1 == 2 AND 1 == 1
returns 0 because OR will return 1 if either the expression on
the left OR the expression on the right (or both) is true,
whereas AND will return 1 only if both the expression on the
left AND the one on the right are true.
In the second case, the AND operator could have known it was
going to return 0 as soon as it evaluated the expression on
the left. Hence there was no need for it to evaluate the
expression on the right. However the OASYS AND and OR
operators are not very intelligent and always evaluate both
expressions even when there is no need. This can be important
when one expression has "side-effects" i.e. causes things to
happen as well as returning a value, such as a method call.
See "Recursion" for an example.
5.16.2.3.4: Operator precedence
In OASYS just as in mathematics some operators are calculated
before others e.g. 1 + 2 * 3 means 1 + (2 * 3) not (1 + 2) *
3. The list of operators is shown below in descending order of
precedence:
* / %
+ -
> < >= <=
== !=
AND
OR
The order is designed so that expressions usually work the way
you expect them to e.g.
A == B AND C == D
means
(A == B) AND (C == D)
rather than
A == (B AND C) == D
as would be the case if AND had a higher precedence than "==".
While legal, the above is probably not what was intended!
The unary minus sign and NOT (q.v.) both have higher
precedence than any normal operator so -5 * 7 means (-5) * 7
rather than -(5 * 7).
5.16.2.4: - expression
A minus sign followed by a number gives the negative of the
number. This is a special case called "unary minus" and is
regarded as quite distinct from the use of the minus sign to
denote subtraction.
The plus sign cannot be used in the same way i.e. +5 on its own
does not mean the same thing as 5, instead it generates an error.
5.16.2.5: NOT expression
The word NOT followed by an expression gives zero if the
expression is nonzero and 1 if the expression is zero, e.g.
NOT 4 == 5
gives 1.
5.16.2.6: (expression)
Surrounding an expression in brackets cause it to be evaluated
first, just like in normal arithmetic e.g.
4 + 5 * 2
gives 4 + 10 = 14 but
(4 + 5) * 2
gives 9 * 2 = 18. This also works with non-arithmetic operators
e.g.
NOT (A == B AND C == D)
is different from
(NOT A == B) AND (C == D)
5.16.2.7: LOAD
LOAD will try to load an old game position recorded with SAVE and
will return 1 if the load succeeded and 0 if it failed. The
reason it returns a value is that it is commonly desired to do
something like:
IF LOAD
PLAYER DESCRIBE_LOCATION
i.e. only if the load succeeded, give the player an immediate
reminder of where he was.
LOAD will prompt the player for the name of the file which
contains the saved game. It will fail if the file cannot be
found. It will also fail if the file is not an OASYS saved game
at all, since such files contain a special signature which OASYS
can recognize. (This signature is different from the signature
which identifies OASYS object files). However the LOAD function
will not be able to detect if the saved game was saved from a
different game. Such cases will produce "unpredictable results"
and it will usually be necessary to reset the computer.
What happens if you are testing a game, have saved a position,
made a slight change to fix a bug and now want to reload that
position to continue where you left off? You can do this *only
if* you have not:
added or removed any global variables
added or removed any properties
added or removed any classes
added or removed any strings
This last is the one that generally creates the most problems. A
favourite trick when debugging a method when something is going
wrong and you're not sure where is to insert statements at
various points like PRINT "Reached point A", PRINT "Reached point
B" etc. This will make your saved games unusable! A possible
alternative is to put in statements like PRINT 1001, PRINT 1002
etc. as substitutes.
[The signature for an OASYS saved game is the 4-byte C string
"oas\1" at the beginning of the file.]
5.16.2.8: RANDOM expression
The expression must give an INT result, and RANDOM will return a
random number between expression - 1 and 0 inclusive e.g.
RANDOM 5
will generate a random number somewhere between 0 and 4. RANDOM 1
will always generate 0 and RANDOM 0 will give a "Division by
zero" error message when the game is run.
[The random number generator is seeded from the system clock at
the start of every game so you should always get different
results.]
5.16.2.9: CREATE class-name
This will create an object of the given class and return a
reference to it e.g.
KITCHEN = CREATE ROOM
Why is it said to return a "reference to" the object rather than
just the object itself? Well suppose you have:
OBJECT SWORD1
OBJECT SWORD2
SWORD1 = CREATE SWORD
SWORD2 = SWORD1
Then there is really only one sword but either of the two
variables can be used to refer to it.
SWORD1 DESC = "A large sword"
PRINT SWORD2 DESC
will give the result
A large sword
and
DESTROY SWORD1
PRINT SWORD2 DESC
(here SWORD1 and SWORD2 must be *global* variables otherwise you
must add
SWORD1 = OBJECT 0
SWORD2 = OBJECT 0
- see the section on "DESTROY" for the reason.)
will give
*NULL STRING*
because SWORD1 and SWORD2 were the same sword and if SWORD1 no
longer exists then of course neither does SWORD2. On the other
hand if you had originally put
SWORD1 = CREATE SWORD
SWORD2 = CREATE SWORD
then you would indeed have two different swords.
5.16.2.10: THIS
THIS will return a reference to the object to which the current
method has been applied. See the earlier section on "THIS" for
further details.
5.16.2.11: OBJECT expression
All objects in the game at any given time are kept in a big list.
OBJECT 1 returns a reference to the first object in this list,
OBJECT 2 returns a reference to the second object in the list and
so on. The expression can be anything that returns an INT value.
Suppose there are currently 200 objects in the game. What happens
if you try to evaluate OBJECT 201 or OBJECT -5? You get the "null
object". This has been called OBJECT 0 throughout this manual
because the expression OBJECT 0 does of course give the null
object, but OBJECT -1 would do as well. So would OBJECT 20000
unless your game is extremely large!
One way to go through every object in the entire system is as
follows:
INT I
I = 1
DO
{
OBJECT I SOME_METHOD
I = I + 1
}
WHILE OBJECT I EXISTS
which will start at object 1 and apply SOME_METHOD to every
object until it comes to the end of the list. However it's more
efficient to use NEXT (q.v.).
Objects are added to the end of the list when CREATEd and removed
from the list when DESTROYed. The order of the list is always
preserved even through a SAVE and LOAD. This means that if you
have created object A before object B and you use the above
technique (or the equivalent with NEXT) to go through the list,
you will always come to A before B.
Also if two or more objects are eligible for a command according
to the selector methods the first one in the list is selected.
This fact is used in ESCAPE.S in the boxes puzzle to ensure that
the player is always trying to OPEN the correct box.
5.16.2.12: Local variable
The name of a local variable is an expression returning the value
of the variable.
5.16.2.13: Method argument
Names of method arguments are equivalent to local variables.
5.16.2.14: Global variable
The name of a global variable is an expression returning the
value of the variable, unless there is a local variable or method
argument with the same name (in which case the local variable or
argument takes priority).
5.16.2.15: expression property-name
The expression must be of type OBJECT. The value of the specified
property of the object will be returned. If the expression is
OBJECT 0 the value of the property will taken to be in accordance
with the rules for uninitialized variables i.e. INT = 0, STRING =
"*NULL STRING*" and OBJECT = OBJECT 0.
5.16.2.16: expression EXISTS
The expression must give a value of type OBJECT. The EXISTS
function will give a value of 1 if the object is not OBJECT 0 and
0 if the object is OBJECT 0.
expression == OBJECT 0 would do the same job, but less
efficiently.
5.16.2.17: expression IS class-name
The expression must give a value of type OBJECT. A value of 1
will be returned if the object's class is the indicated one, 0
otherwise e.g. if you have previously done
KITCHEN = CREATE ROOM
then
KITCHEN IS ROOM
will return 1.
5.16.2.18: expression NEXT
The expression must give a value of type OBJECT. The next object
in the system object list will be returned (see "OBJECT
expression" above). When applied to OBJECT 0 or when the object
is the last in the list, OBJECT 0 is returned. NEXT can be used
to go through all the objects in the game one by one e.g.
OBJECT X
X = OBJECT 1
DO
{
X SOME_METHOD
X = X NEXT
}
WHILE X EXISTS
will apply SOME_METHOD to every object in existence.
5.16.2.19: expression method-name arguments
A method which returns a value of INT, STRING or OBJECT can be
called in an expression. The syntax is above where the first
expression is an object to which the method is to be applied, and
the list of arguments must match those expected by the method in
number, position and type.
The method must return a value, otherwise its call should be a
statement and an attempt to use it in an expression will result
in the error message "Void type used in expression".
5.17: Recursion
(This section is not strictly necessary and may be skipped, but the
material in it will make some kinds of things easier to do).
Since everything must be defined before it is referred to, a method can
only call other methods that have been defined before it. But a method
can also call itself! This is called "recursion". For example, consider
the definition of IS_VISIBLE that says "an object is visible if it is
carried by the player, or in the player's location, or if it is inside
another object which is both open and visible", e.g. something in a
visible open box would also be visible. This can be defined as follows:
METHOD INT IS_VISIBLE
"That isn't here.\n"
{
IF THIS IN == PLAYER OR THIS IN == PLAYER IN
RETURN 1
IF NOT THIS IN EXISTS
RETURN 0
RETURN THIS IN IS_VISIBLE AND THIS IN IS_OPEN
}
[Since local variables are allocated on a stack, each re-entry of a
method gets its own local variable space.]
A recursive definition has been described as "something which is almost
but not quite a circular definition". One pitfall of recursion is of
accidentally leaving out the "almost but not quite" bit. For example,
consider the following:
METHOD INT IS_VISIBLE
"That isn't here.\n"
{
RETURN THIS IN == PLAYER OR THIS IN == PLAYER IN OR
(THIS IN IS_VISIBLE AND THIS IN IS_OPEN)
}
At first glance this looks equivalent to the earlier definition.
However in this case, even if THIS IN == PLAYER IN and the object is in
the player's current location, THIS IN IS_VISIBLE will still be called!
(As explained in "Logical Operators", the OASYS OR is not very clever
and always evaluates all of its arguments even when this should not be
necessary). Similarly when THIS IN IS_VISIBLE is called, it will call
METHOD IS_VISIBLE again and so on forever. Well it would be forever but
OAI will quickly detect this situation when the game is being run and
stop with the message "Stack overflow".
Actually the above definition would work because sooner or later THIS
would equal OBJECT 0 and METHOD IS_VISIBLE would return immediately
without getting a chance to call THIS IN IS_VISIBLE. (Unless of course
you had two objects each of which was inside the other!) But in general
it's something to watch out for.
5.18: Errors
The following sections list various kinds of error messages which the
OAC and OAI programs may give and their causes. For both programs if
you ever see an error message beginning with the words "Assertion
failed", this probably indicates a bug in OASYS. In this case, please
write down the error message and the circumstances under which it
occurred and contact the author.
5.18.1: OAC
Three kinds of error messages may be given by the OAC program:
Normal error messages are indicated by
Error near line xxx: yyy
where xxx is the line number in the source file and yyy is a message
indicating what the problem is.
It frequently happens that OAC does not notice an error until a few
lines after it has happened, so the real error may be a few lines
before the indicated line number. However it will never be after the
indicated line number.
When one or more errors are encountered during translation, the
translation will proceed as normal right up to the point where the
object file has been produced. However, OAI will then delete the
object file. The purpose of this is to make sure that an invalid
object file is never produced, because then the computer could crash
if an attempt was made to run it.
Fatal error messages are indicated by
Fatal error near line xxx: yyy
The difference between normal and fatal errors is that when a fatal
error is encountered, OAI doesn't understand what you are trying to
do and is not able to continue translating your source code. Hence
it will stop immediately and you must fix the problem before trying
again. In this case no object file is created and if there was an
old object file there it is not affected.
Some errors occur when you have no INIT method defined, no strings,
no vocabulary or whatever - the OAI program is designed on the
assumption that these will be present and hence OAC checks for their
presence. If you want to write a throwaway game to test some
language feature, you could use a copy of EXAMPLE.S and modify it,
as it already has all the minimum requirements to translate and run
successfully.
Other miscellaneous errors may also occur for events such as OAC
being unable to find the source file. These normally involve
translation stopping immediately as for a fatal error.
5.18.2: OAI
The following error messages may be given by the OAI program when
running an adventure:
Not an OASYS file
This message can only be given when OAI is trying to load the object
file. It means that the file you have told it to load is not a valid
object file produced by the OAC program.
[The marker for an OASYS object file is the 4-byte C string "oas\0"
at the beginning of the file.]
Division by zero
This means that somewhere in your method code an attempt has been
made to divide by zero (trying to evaluate RANDOM 0 will have this
effect).
Stack overflow
This means that your method code uses too much space in an area of
memory known as the "stack". This is used as a scratchpad when
evaluating expressions, and is also used for storing local variables
and method arguments. A stack overflow will be produced by a runaway
recursion (q.v.), otherwise you will have to reduce the depth of
method calls, number of local variables etc. used by your code. Note
that only the methods being called at the time of the stack overflow
have any effect on the amount of stack space used.
[A small amount of space on the hardware stack is used for each
method call but local variables etc. are allocated on a separate
software stack. It is an overflow of this stack which is indicated
by the error message.]
Nonexistent object
An attempt has been made to DESTROY an object which is OBJECT 0.
6: Guidelines
6.1: Writing Adventures
The following are guidelines on how to write adventure games with
OASYS. You don't have to follow them but you may find them useful.
OASYS is designed to be a relatively easy system with which to add
stuff in as you go along: you can add new global variables, methods,
properties etc. without too much difficulty. (Remember to delete your
old saved positions!) However you should have at least a full map drawn
before you sit down at the computer.
Do things with objects rather than flags whenever possible, e.g. in
ESCAPE there is a situation where the player encounters a tiger while
walking along a corridor! In an older system this might have been
handled by a global variable which recorded whether or not the tiger
was still there or had been killed. In OASYS it was handled instead by
a special object for the tiger - the object was DESTROYed when the
tiger was defeated. This approach lets unusual events be handled easily
(e.g. what happens if the player tries to GET TIGER?).
Follow the "rule of trap the negatives". Suppose you have the player
trying to blow something up with some explosive. You might start off
with
IF NOT EXPLOSIVE IS_CARRIED
{
PRINT "You have no explosive!\n"
RETURN
}
and then
IF NOT DETONATOR IS_CARRIED
{
PRINT "You've nothing to set off the explosive with!\n"
RETURN
}
and after a few such checks, we now know that the player can indeed do
the deed and we have:
PRINT "Boom!\n"
DESTROY X
DESTROY EXPLOSIVE
DESTROY DETONATOR
or whatever. The point here is that you start off by trapping the most
serious problem or "negative" (IF NOT...), with a message and a RETURN
statement. If the method gets past that check you trap the next most
serious problem and so on. Eventually if the method gets past all the
checks you can assume everything is OK and proceed to carry out the
command.
Copy extensively from the example adventures and any others you can get
your hands on. Don't reinvent the wheel!
Follow the conventions for indenting statements within curly brackets,
IF statements etc. as used in EXAMPLE.S and ESCAPE.S.
If your word processor allows it, set tab stops to only three spaces
apart so when there are several levels of indentation in your code you
still have a good deal of room left before you hit the right-hand edge
of the screen.
Back up your source code onto floppy disk, magnetic tape or other
medium after every working session. (It is not necessary to back up the
object file since you can easily recreate it from the source file).
When you get a "bug" or error in your game, which you inevitably will,
use the strategy of "divide and conquer" i.e. put in diagnostic PRINT
statements to tell you the values of variables etc. at various points
in the code. Finding whereabouts things are going wrong is half the
battle. (Remember if you put in any PRINT statements with strings,
delete your old saved positions!)
Put a comment such as //DEBUG beside all the diagnostic statements so
when you do find the bug you can use your word processor's search
facility to find and remove the diagnostics.
Put comments beside any obscure bits of code you write. Even if nobody
else ever reads your code you might want to come back to it in a
month's time.
Before starting to write the Adventure Game To End All Adventure Games,
ask yourself will it fit into your computer's memory? There's no point
in spending weeks writing half of a huge masterpiece and then having to
abandon it because you've run out of memory. Compare your design with
the ESCAPE game to get an estimate of relative sizes.
6.2: Efficiency
The following are guidelines on how to make your game more efficient in
two ways: to use less memory in the computer when running and to run
faster. However where efficiency conflicts with ease of writing and
debugging the adventure, efficiency usually gets lower priority. An
exception sometimes happens when the adventure becomes so big it won't
fit into the computer's memory!
You can reduce memory usage by either reducing the size of the object
file (which must be loaded into memory when the game is run) or by
reducing the number of objects created or the number of object
properties or global variables (which also take up memory).
See your computer's manual for information on how to check the size of
the object file, and the amount of free memory available.
Since you can't reduce the number of objects without making your game
less complex, this is not recommended.
Eliminating an object property will reduce memory usage. Eliminating a
global variable will not significantly reduce memory usage - properties
take up memory for each and every object created, whereas global
variables only take up memory once. Local variables do not take up
memory in the normal sense, but they take up space on the "stack" (see
the "Errors" section) which is also of limited size.
Most of the space in the object file is taken up by actual method code
and strings to be displayed to the player - these tend to be of roughly
equal size. The extra "overhead" information takes up relatively little
space.
You can reduce the size of the object file by reducing the number and
length of your strings. This is a measure of desperation, since it will
reduce the quality of your adventure. Where possible, reducing the size
of the method code while still having it do the same job is the best
course.
In the current version of OASYS, it doesn't matter whether two strings
are the same or different - each one is still stored separately.
However this may change in later versions, so if you have the string
"Fred" several times in your code, it would only take up space once.
(This also affects comparison of strings - see "Comparison Operators"
for further details).
The use of comments and the insertion of extra spaces and blank lines
in your source code makes no difference whatsoever to the efficiency of
the adventure (though they will make the souce code take up more disk
space and take slightly longer to translate into object code), because
comments etc. are not included in the object file.
In general, the use of longer identifiers for things like method and
variable names also makes no difference to efficiency for the above
reason. However due to a quirk of the OAC program design, names of
method arguments are included in the object file. So using shorter
names for method arguments will save a small amount of space, as will
using the same argument names in different methods - if you have many
methods with arguments called FRED, the word FRED is only stored once.
(In future versions, method argument names may not be included in the
object file).
Using THIS is more efficient than using a variable or method argument.
Suppose you have a method in which you know beforehand that THIS will
be the same as PLAYER. (For example, verb methods in a game which does
not allow the player to talk to other objects). Then using THIS instead
of PLAYER will make your adventure more efficient.
Using IS is more efficient than comparing variables. For example,
suppose you have IF THIS == PLAYER in a method. If you have a CLASS
PLAYER of which the global variable PLAYER is the only example, you
could instead write IF THIS IS PLAYER.
When a WHILE loop and a DO...WHILE loop will do the same job (e.g.
because you know the condition will always be true the first time round
the loop), DO...WHILE is more efficient.
7: Versions
The version number of each OASYS program is displayed in a one-line
message when the program is run. (It should be the same for all versions).
This copy of the manual is for version 1.0.
It is quite likely that an object file produced with one version of OAC
will not run with a later version of OAI. In this case OAI will give the
message "Not an OASYS file" when it fails to recognize the object file.
It is less likely but still possible that a source file written for one
version of OAC will not be accepted by a later version of OAC, in other
words that the definition of the language will change.
This means that if you get a later version of OASYS you shouldn't throw
away your old version until you've checked whether or not the later
version accepts your old code.